package no.uio.ifi.cop.common;


import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;

/**
 * A simple logging utility.
 *
 * It can write to multiple sources, the default
 * source is {@link java.lang.System#err}. You can add sources
 * by adding a path to a file. There are 5 different
 * log-levels: DEBUG, INFO, WARNING (default), ERROR, FATAL.
 *
 * Write operations are done using {@link java.io.PrintStream#printf},
 * so a call to Log.warning, Log.debug, Log.info and so on, works like
 * any call to printf.
 *
 * <pre>
 * // illegal
 * Log.warning( someObject ); 
 * // illegal
 * Log.warning( 4 );
 * // legal
 * Log.warning("%s", someObject );
 * // legal
 * Log.warning("%d", 4 ); 
 * </pre>
 *
 *
 * <pre>
 *
 * // Example
 *
 * // this will write someObject.toString() to log-file/stderr
 * // if log-level is set to INFO or DEBUG, otherwise nothing.
 *
 * Log.info("%s", someObject);
 *
 * // fatal also calls {@link java.lang.System#exit} with status 1
 * 
 * Log.fatal("Last log message before System.exit(1)");
 *
 * </pre>
 *
 *
 * @version 1.0
 * @author  bjarneh@ifi.uio.no
 */

// Since java.util.logging and log4j are usually overkill;
// formatting to XML, HTML, or writing to syslog and so
// on seems a bit too much...
//
// NOTE: Log.debug, Log.info, Log.error and so on work
// just like PrintStream.printf, i.e., use a format-string
// and some arguments, or just a plain string. But not
// mixed types, toString is not automatically called, no
// basic types are supported either.
//
// ERROR   Log.info(4);
// OK      Log.info("%d", 4);
// ERROR   Log.info( someObject );
// OK      Log.info("%s", someObject );
// OK      Log.info(someObject.toString());
// OK      Log.info("some string");

public class Log{

    public static final int DEBUG    = 0;
    public static final int INFO     = 1;
    public static final int WARNING  = 2;
    public static final int ERROR    = 3;
    public static final int FATAL    = 4;
    
    private static int LEVEL;
    private static List<PrintStream> output;

    static{
        init();
    }

    private Log(){ }

    private static void init(){
        output   = new ArrayList<PrintStream>(5);
        output.add(System.err);
        LEVEL = WARNING;
    }

    public static void setLevel(int level){
        if( level < 0 || level > 4 ){
            throw new RuntimeException("\nLog-level must be in: 0,1,2,3,4");
        }
        LEVEL = level;
    }

    public static int getLevel(){
        return LEVEL;
    }

    // clear all output sources
    public static void clear(){
        output.clear();
    }

    public static void add(PrintStream stream){
        output.add(stream);
    }

    public static void add(String filename) throws IOException, FileNotFoundException{
        Log.add(new File(filename));
    }

    public static void add(File file) throws IOException, FileNotFoundException{
        if( ! file.exists() ){
            file.createNewFile();
        }
        output.add(new PrintStream(file));
    }

    public static void debug(String fmt, Object ... args){
        if(LEVEL <= DEBUG){
            String message = "[DEBUG] "+Log.logStamp()+fmt+"\n";
            Log.print(message, args);

        }
    }

    public static void info(String fmt, Object ... args){
        if(LEVEL <= INFO){
            String message = "[INFO] "+Log.logStamp()+fmt+"\n";
            Log.print(message, args);
        }
    }

    public static void error(String fmt, Object ... args){
        if(LEVEL <= ERROR){
            String message = "[ERROR] "+Log.logStamp()+fmt+"\n";
            Log.print(message, args);
        }
    }

    public static void warning(String fmt, Object ... args){
        if(LEVEL <= WARNING){
            String message = "[WARNING] "+Log.logStamp()+fmt+"\n";
            Log.print(message, args);
        }
    }

    // log and stop, fatal means fatal
    public static void fatal(String fmt, Object ... args){
        // always true: LEVEL <= FATAL
        String message = "[FATAL] "+Log.logStamp()+fmt+"\n";
        Log.print(message, args);
        Log.close();
        System.exit(1);
    }

    public static void fatalln(String s){
        String message = "[FATAL] "+Log.logStamp()+s+"\n";
        Log.println(message);
        Log.close();
        System.exit(1);
    }

    private static void print(String fmt, Object ... args){
        for(PrintStream stream : output){
            stream.printf(fmt, args);
        }
    }

    private static void println(String s){
        for(PrintStream stream : output){
            stream.println(s);
        }
    }

    private static String logStamp(){
        StackTraceElement stackTrace =  Thread.currentThread().getStackTrace()[3];
        return String.format(" %s | %s:%d\n",
                             new Date(),
                             stackTrace.getFileName(),
                             stackTrace.getLineNumber());
    }

    // close all streams except stderr and stdout
    public static void close(){
        for(PrintStream stream : output){
            if( stream != System.out && stream != System.err ){
                stream.close();
            }
        }
    }

}
